﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows.Media.Media3D;

namespace UI.Control
{

    // Next version :
    // - Set camera
    // - Set lights
    // - Set _viewableCoversOnSide

    /// <summary>
    /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
    ///
    /// Step 1a) Using this custom control in a XAML file that exists in the current project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:CoverFlowControl"
    ///
    ///
    /// Step 1b) Using this custom control in a XAML file that exists in a different project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:CoverFlowControl;assembly=CoverFlowControl"
    ///
    /// You will also need to add a project reference from the project where the XAML file lives
    /// to this project and Rebuild to avoid compilation errors:
    ///
    ///     Right click on the target project in the Solution Explorer and
    ///     "Add Reference"->"Projects"->[Browse to and select this project]
    ///
    ///
    /// Step 2)
    /// Go ahead and use your control in the XAML file.
    ///
    ///     <MyNamespace:CoverFlow3D/>
    ///
    /// </summary>
    sealed public class CoverFlow3D : System.Windows.Controls.Control
    {

        #region Variables

        // The collection to display
        internal ListCollectionView _dataView;

        // Parameters
        internal int _viewableCoversOnSide = 24;

        // Inner datas
        private PerspectiveCamera _camera;
        private ModelVisual3D _visualModel;
        private Viewport3D _viewport;
        private TextBlock _titleBox;
        private TextBlock _subTitleBox;
        private ScrollBar _scrollBar;
        private Button _btnSwitch;

        private List<Cover> _covers = new List<Cover>();

        // Cache
        private string _initialTitle = null;
        private string _initialSubTitle = null;

        // Locks
        private bool _lockCenteredItemIndex = false;
        private bool _lockSelectedIndex = false;

        // Layouts collection
        private List<AbstractCoverLayout> _layouts = new List<AbstractCoverLayout>();
        private int _layoutIndex = 0;

        #endregion

        #region Routed events

        // Events
        public static readonly RoutedEvent CenteredItemChangedEvent =
            EventManager.RegisterRoutedEvent(
                "CenteredItemChanged",
                RoutingStrategy.Bubble,
                typeof(RoutedEventHandler),
                typeof(CoverFlow3D));

        public static readonly RoutedEvent LayoutChangedEvent =
            EventManager.RegisterRoutedEvent(
                "LayoutChanged",
                RoutingStrategy.Bubble,
                typeof(RoutedEventHandler),
                typeof(CoverFlow3D));

        public static readonly RoutedEvent SelectionChangedEvent =
            EventManager.RegisterRoutedEvent(
                "SelectionChanged",
                RoutingStrategy.Bubble,
                typeof(RoutedEventHandler),
                typeof(CoverFlow3D));

        #endregion

        #region Dependency properties

        public static readonly DependencyProperty ItemSizeFactorProperty =
            DependencyProperty.Register(
                "ItemSizeFactor",
                typeof(double),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata(0.3));

        public static readonly DependencyProperty ItemYOffsetProperty =
            DependencyProperty.Register(
                "ItemYOffset",
                typeof(double),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata(0.0));

        public static readonly DependencyProperty CenteredItemIndexProperty =
            DependencyProperty.Register(
                "CenteredItemIndex",
                typeof(int),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata(-1,
                    new PropertyChangedCallback(OnCenteredItemIndexChanged),
                    new CoerceValueCallback(CoerceCenteredItemIndex)));

        public static readonly DependencyProperty SelectedIndexProperty =
            DependencyProperty.Register(
                "SelectedIndex",
                typeof(int),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata(-1,
                    new PropertyChangedCallback(OnSelectedIndexChanged),
                    new CoerceValueCallback(CoerceSelectedIndex)));

        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register(
                "Title",
                typeof(string),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTitleChanged)));

        public static readonly DependencyProperty SubTitleProperty =
            DependencyProperty.Register(
                "SubTitle",
                typeof(string),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnSubTitleChanged)));

        public static readonly DependencyProperty LayoutProperty =
            DependencyProperty.Register(
                "Layout",
                typeof(AbstractCoverLayout),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata(new Layout.DefaultLayout(), new PropertyChangedCallback(OnLayoutChanged)));

        public static readonly DependencyProperty AnimationTimeProperty =
            DependencyProperty.Register(
                "AnimationTime",
                typeof(TimeSpan),
                typeof(CoverFlow3D),
                new FrameworkPropertyMetadata(TimeSpan.FromMilliseconds(500)));

        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.Register(
                "ItemTemplate",
                typeof(DataTemplate),
                typeof(CoverFlow3D));

        #endregion

        #region Constructor

        static CoverFlow3D()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CoverFlow3D), new FrameworkPropertyMetadata(typeof(CoverFlow3D)));
        }

        public CoverFlow3D()
        {
            _layouts.Add(new Layout.DefaultLayout());
            _layouts.Add(new Layout.FlatLayout());
            _layouts.Add(new Layout.ConcaveLayout());
            _layouts.Add(new Layout.ConvexLayout());
            _layouts.Add(new Layout.AngularLayout());
            //_layouts.Add(new Layout.VerticalLayout());

            //---- Set the default background
            LinearGradientBrush background = new LinearGradientBrush();
            background.StartPoint = new Point(0, 0);
            background.EndPoint = new Point(0, 1);
            background.GradientStops.Add(new GradientStop(Color.FromArgb(255, 0, 0, 0), 0));
            background.GradientStops.Add(new GradientStop(Color.FromArgb(255, 0, 0, 0), 0.6));
            background.GradientStops.Add(new GradientStop(Color.FromArgb(255, 255, 255, 255), 0.61));
            background.GradientStops.Add(new GradientStop(Color.FromArgb(255, 221, 221, 221), 1));

            this.Background = background;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Controls
            _visualModel = (ModelVisual3D)Template.FindName("visualModel", this);
            _camera = (PerspectiveCamera)Template.FindName("camera", this);
            _viewport = (Viewport3D)Template.FindName("viewport", this);
            _titleBox = (TextBlock)Template.FindName("titleBox", this);
            _subTitleBox = (TextBlock)Template.FindName("subTitleBox", this);
            _scrollBar = (ScrollBar)Template.FindName("scrollBar", this);
            _btnSwitch = (Button)Template.FindName("btnSwitch", this);

            RowDefinition row = (RowDefinition)Template.FindName("gridGapRow", this);
            row.Height = new GridLength(ItemYOffset);

            // Events
            _viewport.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnViewportMouseDown);
            Application.Current.MainWindow.MouseWheel += new MouseWheelEventHandler(OnViewportMouseWheel);
            Application.Current.MainWindow.PreviewKeyDown += new KeyEventHandler(OnKeyDown);
            _scrollBar.Scroll += new ScrollEventHandler(OnScroll);
            _btnSwitch.Click += new RoutedEventHandler(OnButtonSwitch_Click);

            //---- Initialize some values
            // Will be used before the default property initialization, so we initialize it here
            Layout = _layouts[0];

            if (_dataView != null)
                OnItemsSourceUpdate();

            if (_initialTitle != null)
            {
                _titleBox.Text = _initialTitle;
                _initialTitle = null;
            }

            if (_initialSubTitle != null)
            {
                _subTitleBox.Text = _initialSubTitle;
                _initialSubTitle = null;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the current layout used by the cover-flow.
        /// 
        /// Layouts define how each item is positioned in the cover-flow.
        /// </summary>
        public AbstractCoverLayout Layout
        {
            get
            {
                return (AbstractCoverLayout)GetValue(LayoutProperty);
            }
            set
            {
                SetValue(LayoutProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a collection used to generate the content.
        /// </summary>
        public object ItemsSource
        {
            set
            {
                if (_dataView != null && _dataView is INotifyCollectionChanged)
                {
                    INotifyCollectionChanged observable = _dataView as INotifyCollectionChanged;
                    observable.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnCollectionChanged);
                }

                _dataView = CollectionViewSource.GetDefaultView(value) as ListCollectionView;

                // Center on the collection
                if (_dataView.Count > 0)
                {
                    _lockCenteredItemIndex = true;

                    CenteredItemIndex = (int)Math.Round(Math.Min(_dataView.Count, _viewableCoversOnSide * 2.0) / 2);

                    RaiseEvent(new RoutedEventArgs(CenteredItemChangedEvent));

                    _lockCenteredItemIndex = false;
                }

                OnItemsSourceUpdate();
            }
        }

        /// <summary>
        /// Gets or sets the index of the item centered on the control, or returns
        ///  negative one (-1) if there is no item. This is a dependency property.
        /// </summary>
        public int CenteredItemIndex
        {
            get
            {
                return (int)GetValue(CenteredItemIndexProperty);
            }
            set
            {
                SetValue(CenteredItemIndexProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the index of the current selection or returns
        ///  negative one (-1) if the selection is empty. This is a dependency property.
        /// </summary>
        public int SelectedIndex
        {
            get
            {
                return (int)GetValue(SelectedIndexProperty);
            }
            set
            {
                SetValue(SelectedIndexProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the main title displayed on the top of the control.
        /// </summary>
        public string Title
        {
            get
            {
                return (string)GetValue(TitleProperty);
            }
            set
            {
                SetValue(TitleProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the sub title displayed on the top of the control.
        /// </summary>
        public string SubTitle
        {
            get
            {
                return (string)GetValue(SubTitleProperty);
            }
            set
            {
                SetValue(SubTitleProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the size factor for the items/covers.
        /// Default value is 0.3
        /// </summary>
        public double ItemSizeFactor
        {
            get
            {
                return (double)GetValue(ItemSizeFactorProperty);
            }
            set
            {
                SetValue(ItemSizeFactorProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the offset applied on the Y axis for the items/covers.
        /// Default value is 0
        /// </summary>
        public double ItemYOffset
        {
            get
            {
                return (double)GetValue(ItemYOffsetProperty);
            }
            set
            {
                SetValue(ItemYOffsetProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the time duration used for the animations.
        /// </summary>
        public TimeSpan AnimationTime
        {
            get
            {
                return (TimeSpan)GetValue(AnimationTimeProperty);
            }
            set
            {
                SetValue(AnimationTimeProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the System.Windows.DataTemplate used to display each item. This
        /// is a dependency property.
        /// </summary>
        /// <returns>
        /// A System.Windows.DataTemplate that specifies the visualization of the data objects. The default is null.
        /// </summary>
        public DataTemplate ItemTemplate
        {
            get
            {
                return (DataTemplate)GetValue(ItemTemplateProperty);
            }
            set
            {
                SetValue(ItemTemplateProperty, value);
            }
        }

        #endregion

        #region Routed events CLR wrappers

        public event RoutedEventHandler CenteredItemChanged
        {
            add { AddHandler(CoverFlow3D.CenteredItemChangedEvent, value); }
            remove { RemoveHandler(CoverFlow3D.CenteredItemChangedEvent, value); }
        }

        public event RoutedEventHandler SelectionChanged
        {
            add { AddHandler(CoverFlow3D.SelectionChangedEvent, value); }
            remove { RemoveHandler(CoverFlow3D.SelectionChangedEvent, value); }
        }

        public event RoutedEventHandler LayoutChanged
        {
            add { AddHandler(CoverFlow3D.LayoutChangedEvent, value); }
            remove { RemoveHandler(CoverFlow3D.LayoutChangedEvent, value); }
        }

        #endregion

        #region SwitchLayout

        private void SwitchLayout()
        {
            _layoutIndex++;
            if (_layoutIndex >= _layouts.Count)
                _layoutIndex = 0;

            Layout = _layouts[_layoutIndex];
        }

        #endregion

        #region DataTemplate management

        internal FrameworkElement GenerateContent(object data, bool isSelected)
        {
            //---- Create an empty content
            Border border = new Border();
            border.Padding = new Thickness(10);
            border.CornerRadius = new CornerRadius(2);
            //border.BorderThickness = new Thickness(6);
            if (isSelected)
            {
                border.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 200, 0, 0));
                border.Background = border.BorderBrush;
            }
            else
            {
                border.BorderBrush = new SolidColorBrush(Color.FromArgb(0, 255, 255, 255));
                border.Background = border.BorderBrush;
            }

            FrameworkElement element = ItemTemplate.LoadContent() as FrameworkElement;
            border.Child = element;

            //---- Set up the data binding
            Binding binding = new Binding();
            binding.Source = data;
            BindingOperations.SetBinding(element, FrameworkElement.DataContextProperty, binding);

            return border;
        }

        #endregion

        #region OnItemsSourceUpdate

        private void OnItemsSourceUpdate()
        {
            // The control is not really initialized
            if (_visualModel == null)
                return;

            // Clear
            _covers.Clear();
            _visualModel.Children.Clear();

            _scrollBar.Maximum = _dataView.Count - 1;
            _scrollBar.Value = CenteredItemIndex;

            // Add all the covers
            int minIndex = Math.Max(0, CenteredItemIndex - _viewableCoversOnSide);
            int maxIndex = Math.Min(_dataView.Count - 1, CenteredItemIndex + _viewableCoversOnSide);

            for (int collectionIndex = minIndex; collectionIndex <= maxIndex; collectionIndex++)
            {
                object data = _dataView.GetItemAt(collectionIndex);

                int visualIndex = collectionIndex - CenteredItemIndex;

                CreateCover(data, collectionIndex, visualIndex);
            }

            if (_dataView is INotifyCollectionChanged)
            {
                INotifyCollectionChanged observable = _dataView as INotifyCollectionChanged;
                observable.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
            }
        }

        #endregion

        #region OnCenteredItemIndexChanged

        private static object CoerceCenteredItemIndex(DependencyObject d, object value)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;

            if (coverFlow._lockCenteredItemIndex)
                return value;

            // If the control is not yet initialized
            if (coverFlow._dataView == null)
                return value;

            // Check if the position is valid
            int newPosition = (int)value;

            if (newPosition == coverFlow.CenteredItemIndex)
                return coverFlow.CenteredItemIndex;

            if (newPosition >= coverFlow._dataView.Count)
                return coverFlow.CenteredItemIndex;

            if (newPosition < 0)
                return coverFlow.CenteredItemIndex;

            return value;
        }

        private static void OnCenteredItemIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;

            if (coverFlow._lockCenteredItemIndex)
                return;

            int oldPosition = (int)e.OldValue;

            coverFlow.ProcessScrollPositionChanged(oldPosition);

            coverFlow._scrollBar.Value = (int)e.NewValue;

            coverFlow.RaiseEvent(new RoutedEventArgs(CenteredItemChangedEvent));
        }

        private void ProcessScrollPositionChanged(int oldPosition)
        {
            //----
            int delta = oldPosition - CenteredItemIndex;

            //---- Update all the other covers
            for (int index = 0; index < _covers.Count; index++)
                _covers[index].OnUpdateVisualIndex(delta);

            //---- Delete the covers that are no more visibles
            for (int index = _covers.Count - 1; index > -1; index--)
            {
                if (_covers[index].CollectionIndex < (CenteredItemIndex - _viewableCoversOnSide) ||
                    _covers[index].CollectionIndex > (CenteredItemIndex + _viewableCoversOnSide))
                    DeleteCover(_covers[index]);
            }

            //---- Add the new covers

            // at the end
            int maxIndex = Math.Min(_dataView.Count - 1, CenteredItemIndex + _viewableCoversOnSide);
            int minIndex = Math.Max(0, CenteredItemIndex);
            for (int collectionIndex = minIndex; collectionIndex <= maxIndex; collectionIndex++)
            {
                object data = _dataView.GetItemAt(collectionIndex);

                int visualIndex = collectionIndex - CenteredItemIndex;

                if (FindCoverAt(collectionIndex) == null)
                    CreateCover(data, collectionIndex, visualIndex);
            }

            // at the begining
            minIndex = Math.Max(0, CenteredItemIndex - _viewableCoversOnSide);
            maxIndex = Math.Min(_dataView.Count - 1, CenteredItemIndex);
            for (int collectionIndex = minIndex; collectionIndex <= maxIndex; collectionIndex++)
            {
                object data = _dataView.GetItemAt(collectionIndex);

                int visualIndex = collectionIndex - CenteredItemIndex;

                if (FindCoverAt(collectionIndex) == null)
                    CreateCover(data, collectionIndex, visualIndex);
            }
        }

        #endregion

        #region OnSelectedIndexChanged

        private static object CoerceSelectedIndex(DependencyObject d, object value)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;

            if (coverFlow._lockSelectedIndex)
                return value;

            // If the control is not yet initialized
            if (coverFlow._dataView == null)
                return value;

            if ((int)value == coverFlow.SelectedIndex)
                return coverFlow.SelectedIndex;

            if ((int)value >= coverFlow._dataView.Count)
                return coverFlow.SelectedIndex;

            if ((int)value < 0)
                return coverFlow.SelectedIndex;

            return value;
        }

        private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;

            coverFlow.VerifySelectedIndex();

            coverFlow.RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
        }

        private void VerifySelectedIndex()
        {
            int oldIndex = SelectedIndex;

            if (_dataView.Count == 0)
            {
                _lockSelectedIndex = true;
                SelectedIndex = -1;
                _lockSelectedIndex = false;
            }

            else if (SelectedIndex >= _dataView.Count)
                SelectedIndex = _dataView.Count - 1;

            else if (SelectedIndex < 0)
                SelectedIndex = 0;

            // Update the selection
            foreach (Cover cover in _covers)
                cover.IsSelected = (cover.CollectionIndex == SelectedIndex);
        }

        #endregion

        #region OnViewportMouseDown

        private void OnViewportMouseDown(object sender, MouseButtonEventArgs e)
        {
            // We search for the cover hitted by the mouse
            var rayMeshResult = (RayMeshGeometry3DHitTestResult)VisualTreeHelper.HitTest(_viewport, e.GetPosition(_viewport));

            if (rayMeshResult == null)
                return;

            for (int index = 0; index < _covers.Count; index++)
                if (_covers[index].Matches(rayMeshResult.MeshHit))
                {
                    if (e.ClickCount == 1)
                        CenteredItemIndex = _covers[index].CollectionIndex;
                    else if (e.ClickCount == 2)
                        SelectedIndex = CenteredItemIndex;
                    return;
                }
        }

        #endregion

        #region OnKeyDown

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Left)
                CenteredItemIndex--;
            else if (e.Key == Key.Right)
                CenteredItemIndex++;
            else if (e.Key == Key.F12)
                SwitchLayout();
        }

        #endregion

        #region OnViewportMouseWheel

        private void OnViewportMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
                CenteredItemIndex++;

            if (e.Delta < 0)
                CenteredItemIndex--;
        }

        #endregion

        #region OnLayoutPropertyChanged

        /// <summary>
        /// This method is called each time a layout change one of its property.
        /// This force a layout refresh.
        /// 
        /// You can use this method if you create your own Layout implementation.
        /// </summary>
        public void OnLayoutPropertyChanged()
        {
            for (int index = 0; index < _covers.Count; index++)
                _covers[index].OnLayoutPropertyChanged();
        }

        #endregion

        #region OnTitleChanged

        private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;
            if (coverFlow._titleBox != null)
                coverFlow._titleBox.Text = (string)e.NewValue;
            else
                coverFlow._initialTitle = (string)e.NewValue;
        }

        #endregion

        #region OnSubTitleChanged

        private static void OnSubTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;
            if (coverFlow._subTitleBox != null)
                coverFlow._subTitleBox.Text = (string)e.NewValue;
            else
                coverFlow._initialSubTitle = (string)e.NewValue;
        }

        #endregion

        #region OnLayoutChanged

        private static void OnLayoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;
            AbstractCoverLayout layout = e.NewValue as AbstractCoverLayout;

            layout.CoverFlow = coverFlow;

            coverFlow.OnLayoutPropertyChanged();

            coverFlow.RaiseEvent(new RoutedEventArgs(LayoutChangedEvent));
        }

        #endregion

        #region OnButtonSwitch_Click

        private void OnButtonSwitch_Click(object sender, RoutedEventArgs e)
        {
            SwitchLayout();
        }

        #endregion

        #region OnScroll

        private void OnScroll(object sender, ScrollEventArgs e)
        {
            CenteredItemIndex = (int)_scrollBar.Value;
        }

        #endregion

        #region OnCollectionChanged

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    OnCollectionAdd(e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Move:
                    OnCollectionMove(e.OldStartingIndex, e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    OnCollectionRemove(e.OldStartingIndex, true);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    OnCollectionReplace(e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    OnCollectionReset();
                    break;
            }
        }

        #endregion

        #region OnCollectionAdd

        private void OnCollectionAdd(int additionIndex)
        {
            // Update the collection index
            foreach (Cover cover in _covers)
                if (cover.CollectionIndex >= additionIndex)
                    cover.OnUpdateCollectionIndex(cover.CollectionIndex + 1);

            // Update the visual index
            foreach (Cover cover in _covers)
                if (additionIndex < CenteredItemIndex)
                {
                    if (cover.CollectionIndex < additionIndex)
                        cover.OnUpdateVisualIndex(-1);
                }
                else
                    if (additionIndex >= CenteredItemIndex)
                    {
                        if (cover.CollectionIndex > additionIndex)
                            cover.OnUpdateVisualIndex(+1);
                    }

            // Update the selected index
            if (additionIndex < CenteredItemIndex || CenteredItemIndex == -1)
            {
                _lockCenteredItemIndex = true;
                CenteredItemIndex++;
                _lockCenteredItemIndex = false;
            }

            // New visual index
            int newVisualIndex = additionIndex - CenteredItemIndex;

            // Add the new cover
            int minIndex = Math.Max(0, CenteredItemIndex - _viewableCoversOnSide);
            int maxIndex = Math.Min(_dataView.Count - 1, CenteredItemIndex + _viewableCoversOnSide);
            if (additionIndex >= minIndex && additionIndex <= maxIndex)
                CreateCover(_dataView.GetItemAt(additionIndex), additionIndex, newVisualIndex);

            // Update the scrollbar
            _scrollBar.Maximum = _dataView.Count - 1;

            // Verify the selected index
            VerifySelectedIndex();
        }

        #endregion

        #region OnCollectionRemove

        private void OnCollectionRemove(int removeIndex, bool refresh)
        {
            bool isCentralItem = false;
            if (refresh)
                isCentralItem = (removeIndex == CenteredItemIndex);

            // Remove the cover
            foreach (Cover cover in _covers)
                if (cover.CollectionIndex == removeIndex)
                {
                    DeleteCover(cover);
                    break;
                }

            // Shift orientation
            bool shiftLeft = (removeIndex > CenteredItemIndex); // Remove after selection
            bool shiftRight = (removeIndex < CenteredItemIndex); // Remove before selection
            if (removeIndex == CenteredItemIndex)
            {
                if (_dataView.Count == 0)
                { }
                else if (CenteredItemIndex == _dataView.Count)
                    shiftRight = true;
                else if (CenteredItemIndex == 0)
                    shiftLeft = true;
                else shiftLeft = true;
            }

            // Update the selected index
            if (shiftRight)
            {
                _lockCenteredItemIndex = true;
                CenteredItemIndex--;
                _lockCenteredItemIndex = false;
            }

            // Update the visual index
            foreach (Cover cover in _covers)
                if (shiftRight)
                {
                    if (cover.CollectionIndex < removeIndex)
                        cover.OnUpdateVisualIndex(+1);
                }
                else if (shiftLeft)
                {
                    if (cover.CollectionIndex > removeIndex)
                        cover.OnUpdateVisualIndex(-1);
                }

            // Update the collection index
            foreach (Cover cover in _covers)
                if (cover.CollectionIndex > removeIndex)
                    cover.OnUpdateCollectionIndex(cover.CollectionIndex - 1);

            // Insure that all covers are visibles
            if (refresh)
                ProcessScrollPositionChanged(CenteredItemIndex);

            // Update the scrollbar
            _scrollBar.Maximum = _dataView.Count - 1;

            // Verify the selected index
            VerifySelectedIndex();

            if (isCentralItem)
                RaiseEvent(new RoutedEventArgs(CenteredItemChangedEvent));
        }

        #endregion

        #region OnCollectionReset

        private void OnCollectionReset()
        {
            for (int index = _covers.Count - 1; index > -1; index--)
                DeleteCover(_covers[index]);

            _lockCenteredItemIndex = true;
            CenteredItemIndex = -1;
            _lockCenteredItemIndex = false;

            // Update the scrollbar
            _scrollBar.Maximum = 0;

            // Verify the selected index
            VerifySelectedIndex();

            RaiseEvent(new RoutedEventArgs(CenteredItemChangedEvent));
        }

        #endregion

        #region OnCollectionMove

        private void OnCollectionMove(int from, int to)
        {
            bool isCentralItem = (from == CenteredItemIndex);

            // Remove (without refresh and add)
            OnCollectionRemove(from, false);
            OnCollectionAdd(to);

            //---- Insure that all covers are visibles
            ProcessScrollPositionChanged(CenteredItemIndex);

            VerifySelectedIndex();

            if (isCentralItem)
                RaiseEvent(new RoutedEventArgs(CenteredItemChangedEvent));
        }

        #endregion

        #region OnCollectionReplace

        private void OnCollectionReplace(int indexToReplace)
        {
            foreach (Cover cover in _covers)
                if (cover.CollectionIndex == indexToReplace)
                {
                    cover.OnUpdateContent(_dataView.GetItemAt(indexToReplace));
                    return;
                }
        }

        #endregion

        #region OnItemTemplateChanged

        private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CoverFlow3D coverFlow = d as CoverFlow3D;
        }

        #endregion

        #region Helpers

        private Cover FindCoverAt(int index)
        {
            return _covers.Find(delegate(Cover cover) { return cover.CollectionIndex == index; });
        }

        private Cover FindCoverAtVisual(int index)
        {
            return _covers.Find(delegate(Cover cover) { return cover.VisualIndex == index; });
        }

        private void CreateCover(object data, int collectionIndex, int visualIndex)
        {
            UIElement content = GenerateContent(data, false) as UIElement;

            Cover cover = new Cover(this, collectionIndex, visualIndex, content);

            _covers.Add(cover);

            _visualModel.Children.Add(cover);
        }

        private void DeleteCover(Cover cover)
        {
            _covers.Remove(cover);
            _visualModel.Children.Remove(cover);
        }

        #endregion

        #region OnRender

        protected override void OnRender(DrawingContext dc)
        {
            Brush background = this.Background;
            if (background != null)
                dc.DrawRectangle(background, null, new Rect(RenderSize));
        }

        #endregion

        #region To do...

        //    public static readonly DependencyProperty CameraProperty =
        //DependencyProperty.Register("Camera", typeof(PerspectiveCamera), typeof(UIFlow3D), new UIPropertyMetadata(null, OnCameraChanged));

        //    public static readonly DependencyProperty LightProperty =
        //        DependencyProperty.Register("Light", typeof(Light), typeof(UIFlow3D), new UIPropertyMetadata(null, OnLightChanged));

        //}

        //        private static void OnLightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        //    {
        //        Light light = e.NewValue as Light;

        //        if (light == null)
        //            return;

        //        UIFlow3D flow = d as UIFlow3D;

        //        if (flow == null)
        //            return;

        //        Model3DGroup l瓥ghtContainer = flow._viewport.FindName("LightContainer") as Model3DGroup;

        //        lightContainer.Children.Clear();
        //        lightContainer.Children.Add(light);
        //    }

        //    private static void OnCameraChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        //    {
        //        PerspectiveCamera camera = e.NewValue as PerspectiveCamera;
        //        if (camera == null)
        //            return;

        //        UIFlow3D flow = d as UIFlow3D;

        //        if (flow == null)
        //            return;

        //        flow._viewport.Camera = camera;
        //    }

        //        public Light Light
        //    {
        //        get { return (Light)GetValue(LightProperty); }
        //        set { SetValue(LightProperty, value); }
        //    }

        //    public PerspectiveCamera Camera
        //    {
        //        get { return (PerspectiveCamera)GetValue(CameraProperty); }
        //        set { SetValue(CameraProperty, value); }
        //    }

        #endregion

    }
}
